<template>
	<view class="rich-content" @tap="handleTap">
		<rich-text
			:nodes="parsedNodes"
			:style="computedStyle"
			class="rich-text-wrapper"
		/>
	</view>
</template>

<script>
import { marked } from 'marked';

// ========================
// 全局缓存：避免重复解析相同内容
// ========================
const parseCache = new Map(); // key: `${type}::${content}`, value: parsedNodes

// ========================
// 渲染器（同前，略作精简）
// ========================
const renderer = new marked.Renderer();

function renderTokensToHtml(tokens) {
	const cleanRenderer = new marked.Renderer();
	return marked.parser(tokens, {
		renderer: cleanRenderer,
		gfm: true,
		breaks: false,
		smartLists: true,
		smartypants: false,
	});
}

renderer.heading = (text, level) =>
	`<p class="heading heading-${level}"><strong>${'#'.repeat(
		level
	)} ${text}</strong></p>`;
renderer.hr = () => `<p class="hr">──────────────</p>`;

renderer.blockquote = tokens => {
	const innerHTML = renderTokensToHtml(tokens);
	let cleanText = innerHTML
		.replace(/<p>/g, '')
		.replace(/<\/p>/g, '\n')
		.replace(/\n+/g, '\n')
		.trim();
	const lines = cleanText.split('\n').filter(l => l.trim());
	const quoted = lines.map(line => `> ${line}`).join('\n');
	return `<p class="blockquote">${quoted}</p>`;
};

renderer.codespan = code => `\`${String(code ?? '')}\``;
renderer.code = (code, language) => {
	const codeStr = String(code ?? '');
	const lang = language ? `[${language}]\n` : '';
	const lines = codeStr.split('\n').filter(l => l.trim() !== '');
	const indented = lines.map(line => `  ${line}`).join('\n');
	return `<pre class="code-block">${lang}${indented}</pre>`;
};

renderer.image = (href, title, text) => {
	if (!href) return '';
	const src = String(href).replace(/"/g, '&quot;');
	const alt = String(text || '').replace(/"/g, '&quot;');
	const t = String(title || '').replace(/"/g, '&quot;');
	return `<img src="${src}" data-src="${src}" alt="${alt}" title="${t}" class="rich-img">`;
};

renderer.link = (href, title, text) => {
	if (!href) return text || '';
	const url = String(href).replace(/"/g, '&quot;');
	const txt = String(text).replace(/"/g, '&quot;');
	const t = String(title || '').replace(/"/g, '&quot;');
	return `<a href="${url}" data-href="${url}" title="${t}" class="rich-link">${txt}</a>`;
};

// ========================
// 工具函数（同前）
// ========================
function convertUnsupportedHtmlTags(html) {
	if (!html) return html;
	html = html.replace(
		/<h([1-6])\b[^>]*>(.*?)<\/h\1>/gi,
		(_, level, content) =>
			`<p class="heading heading-${level}"><strong>${'#'.repeat(
				level
			)} ${content}</strong></p>`
	);
	html = html.replace(/<hr\b[^>]*>/gi, '<p class="hr">──────────────</p>');
	html = html.replace(
		/<blockquote\b[^>]*>((?:.|\n)*?)<\/blockquote>/gi,
		(_, inner) => {
			let text = inner
				.replace(/<[^>]+>/g, ' ')
				.replace(/\s+/g, ' ')
				.trim();
			if (!text) return '<p class="blockquote">&gt; </p>';
			const lines = text.split(/[\n\r]+/).filter(l => l.trim());
			const quoted = lines.map(line => `> ${line.trim()}`).join('\n');
			return `<p class="blockquote">${quoted}</p>`;
		}
	);
	html = html.replace(
		/<code\b[^>]*>((?:.|\n)*?)<\/code>/gi,
		(_, inner) => `\`${inner.replace(/<[^>]+>/g, '')}\``
	);
	html = html.replace(/<pre\b[^>]*>((?:.|\n)*?)<\/pre>/gi, (_, inner) => {
		let clean = inner.replace(/<[^>]+>/g, '');
		const lines = clean.split(/[\n\r]+/).filter(l => l.trim());
		const indented = lines.map(line => `  ${line.trim()}`).join('\n');
		return `<pre class="code-block">${indented}</pre>`;
	});
	html = html.replace(/<dl\b[^>]*>((?:.|\n)*?)<\/dl>/gi, inner =>
		inner
			.replace(/<dt\b[^>]*>/gi, '<p class="dt"><strong>')
			.replace(/<\/dt>/gi, ':</strong></p>')
			.replace(/<dd\b[^>]*>/gi, '<p class="dd">　　')
			.replace(/<\/dd>/gi, '</p>')
	);
	return html;
}

function sanitizeHtml(html) {
	if (!html) return '';
	return html
		.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
		.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
		.replace(/<iframe|<object|<embed/gi, '')
		.replace(/on\w+="[^"]*"/gi, '')
		.replace(/javascript:/gi, 'javascript-disabled:');
}

// ========================
// 组件定义（优化版）
// ========================
export default {
	name: 'RichContent',
	props: {
		content: { type: String, default: '' },
		type: {
			type: String,
			default: 'auto',
			validator: v => ['html', 'markdown', 'auto'].includes(v),
		},
		customStyle: { type: [String, Object], default: '' },
		previewImage: { type: Boolean, default: true },
		// 新增：是否启用防抖（适用于实时输入场景）
		debounce: { type: Number, default: 0 }, // 单位 ms
	},
	data() {
		return {
			parsedNodes: [],
			debounceTimer: null,
		};
	},
	computed: {
		computedStyle() {
			if (typeof this.customStyle === 'string') return this.customStyle;
			return Object.entries(this.customStyle)
				.map(([k, v]) => `${k}: ${v}`)
				.join('; ');
		},
		cacheKey() {
			return `${this.type}::${this.content}`;
		},
	},
	watch: {
		content: {
			handler() {
				this.scheduleParse();
			},
			immediate: true,
		},
		type: {
			handler() {
				this.scheduleParse();
			},
		},
		debounce: {
			handler() {
				// debounce 改变时重置
				this.scheduleParse();
			},
		},
	},
	beforeDestroy() {
		if (this.debounceTimer) {
			clearTimeout(this.debounceTimer);
		}
	},
	methods: {
		isMarkdown(str) {
			if (!str || typeof str !== 'string' || str.length < 10) return false;
			const markdownChars = str
				.split('')
				.filter(c => '*_#-[]()!`'.includes(c)).length;
			return markdownChars >= 2 && /[#*-] |```|$$.*?$$$.*?$/.test(str);
		},

		scheduleParse() {
			// 清除旧定时器
			if (this.debounceTimer) {
				clearTimeout(this.debounceTimer);
			}

			if (this.debounce > 0) {
				this.debounceTimer = setTimeout(() => {
					this.parseContent();
				}, this.debounce);
			} else {
				this.parseContent();
			}
		},

		parseContent() {
			const { content, type } = this;

			// 空内容直接清空
			if (!content?.trim()) {
				this.parsedNodes = [];
				return;
			}

			const key = this.cacheKey;

			// ✅ 1. 优先从缓存读取
			if (parseCache.has(key)) {
				this.parsedNodes = parseCache.get(key);
				return;
			}

			let html = '';
			try {
				if (type === 'markdown') {
					html = marked(content, { renderer });
				} else if (type === 'html') {
					html = convertUnsupportedHtmlTags(sanitizeHtml(content));
				} else if (type === 'auto') {
					html = this.isMarkdown(content)
						? marked(content, { renderer })
						: convertUnsupportedHtmlTags(sanitizeHtml(content));
				}

				html = sanitizeHtml(html);
				const result = [{ html }];
				// ✅ 2. 写入缓存
				parseCache.set(key, result);
				this.parsedNodes = result;
			} catch (err) {
				console.error('RichContent 解析失败:', err);
				const errorResult = [{ text: '[内容解析失败，请检查格式]' }];
				parseCache.set(key, errorResult); // 错误也缓存，避免反复报错
				this.parsedNodes = errorResult;
			}
		},

		handleTap(e) {
			const target = e.target;
			if (!target) return;

			if (this.previewImage && target.dataset?.src) {
				const src = String(target.dataset.src).trim();
				if (src && (src.startsWith('http') || src.startsWith('//'))) {
					uni.previewImage({ urls: [src] });
				}
				return;
			}

			if (target.dataset?.href) {
				let url = String(target.dataset.href).trim();
				if (!url) return;
				if (!url.startsWith('http')) {
					url = url.startsWith('//') ? 'https:' + url : 'https://' + url;
				}

				if (process.env.VUE_APP_PLATFORM === 'h5') {
					window.open(url, '_blank');
				} else {
					uni
						.navigateTo({
							url: `/pages/webview/webview?url=${encodeURIComponent(url)}`,
						})
						.catch(() => {
							uni.showToast({ title: '无法跳转链接', icon: 'none' });
						});
				}
			}
		},
	},
};
</script>

<style scoped>
/* 样式保持不变 */
.rich-content {
	width: 100%;
	overflow: hidden;
	box-sizing: border-box;
}
.rich-text-wrapper {
	width: 100%;
	display: block;
	font-size: 16px;
	line-height: 1.6;
	color: #333;
	word-wrap: break-word;
	white-space: pre-line;
}
.rich-text-wrapper .heading {
	margin: 16px 0 12px;
	font-weight: bold;
}
.rich-text-wrapper .hr {
	text-align: center;
	color: #ccc;
	margin: 20px 0;
}
.rich-text-wrapper .blockquote {
	color: #666;
	border-left: 3px solid #ddd;
	padding-left: 12px;
	margin: 12px 0;
}
.rich-text-wrapper .code-block {
	display: block;
	background: #f8f8f8;
	padding: 12px;
	border-radius: 4px;
	font-family: monospace;
	font-size: 14px;
	overflow-x: auto;
	white-space: pre;
}
.rich-text-wrapper .rich-img {
	max-width: 100%;
	height: auto;
	display: block;
	margin: 12px auto;
}
.rich-text-wrapper .rich-link {
	color: #1890ff;
	text-decoration: underline;
}
</style>
